#include    "../FrameWindow.h"

#include    "WxLibEnvelope.h"
#include    <cassert>
#include    <boost/bind.hpp>            // boost::bind
#include    <memory>                    // std::auto_ptr

#include <sstream>

namespace alfs{

    namespace detail{

        void addItemsTo( wxMenu* pMenu, InvocableUserCommandTree::Ptr commandTree )
        {
            typedef InvocableUserCommandTree::Iterator  TreeIter;

            TreeIter const  end     = commandTree->end();
            for( TreeIter it = commandTree->begin();  it != end;  ++it )
            {
                if( it->isCommand() )
                {
                    InvocableUserCommand::Ptr const pCommand    = it->command();
                    pMenu->Append( pCommand->id(), pCommand->text().c_str() );
                }
                else
                {
                    InvocableUserCommandTree::Ptr const subTree  = it->tree();
                    std::auto_ptr<wxMenu>               pSubMenu( new wxMenu );

                    addItemsTo( pSubMenu.get(), subTree );
                    pMenu->AppendSubMenu( pSubMenu.get(), subTree->text().c_str() );
                    pSubMenu.release();
                }
            }
        }

        void addItemsTo( wxMenuBar* pMenu, InvocableUserCommandTree::Ptr commandTree )
        {
            typedef InvocableUserCommandTree::Iterator  TreeIter;

            TreeIter const  end     = commandTree->end();
            for( TreeIter it = commandTree->begin();  it != end;  ++it )
            {
                (it->isTree()) || throwX(
                    "alfs::detail::addItemsTo(wxMenuBar*): "
                    "direct menu item at top level (not supported, you can only use sub-menus here)"
                    );

                InvocableUserCommandTree::Ptr const subTree   = it->tree();
                std::auto_ptr<wxMenu>               pSubMenu( new wxMenu );

                addItemsTo( pSubMenu.get(), subTree );
                pMenu->Append( pSubMenu.get(), subTree->text().c_str() );
                pSubMenu.release();
            }
        }

        std::auto_ptr<wxMenuBar> wxMenuBarFrom( InvocableUserCommandTree::Ptr commandTree )
        {
            std::auto_ptr<wxMenuBar>    result( new wxMenuBar() );
            if( !commandTree.isVoid() ) { addItemsTo( result.get(), commandTree ); }
            return result;
        }
    }  // detail

    template<>
    inline void destroyPointee<wxFrame>( wxFrame* p )
    {
        if( p != 0 ) { p->Destroy(); }
    }

    void destroyPointee( wxFrame* p )
    {
        destroyPointee<wxFrame>( p );
    }

    long    nCommandEvents  = 0;

    class FrameWindow::Impl
        : public detail::wxLib::Client
        , public wxFrame
    {
    private:
        typedef wxFrame     BaseClass;

        FrameWindow*    myOwnerFrame;

        virtual bool ProcessEvent( wxEvent& event )         // ovrd
        {
            if( event.IsCommandEvent() )
            {
                wxCommandEvent const* const pCommandEvent   =
                    wxDynamicCast( &event, wxCommandEvent );

                // For CreateWindow events the dynamic cast yields 0.
                if( pCommandEvent != 0 )
                {
                    int const eventType = pCommandEvent->GetEventType();

                    if(
                        eventType == wxEVT_COMMAND_MENU_SELECTED ||
                        eventType == wxEVT_COMMAND_BUTTON_CLICKED
                        )
                    {
                        myOwnerFrame->onCommand( UserCommand::Id( pCommandEvent->GetId() ) );
                        // wxWidgets has no default command handling, but does propagate the events:
                        return true;    // Stops propagation.
                    }
                }
            }
            return BaseClass::ProcessEvent( event );
        }

    public:
        Impl(
            FrameWindow*                    pFrame,
            wchar_t const                   title[],
            Window::VisibilityEnum const    visibility
            )
            : wxFrame(NULL, wxID_ANY, title)
            , myOwnerFrame( pFrame )
        {
            assert( !wxFrame::IsShown() );
            if( visibility == Window::visible )
            {
                wxFrame::Show();
            }
            assert( wxFrame::IsShown() == (visibility == visible) );
        }

        void setMainMenu( InvocableUserCommandTree::Ptr commandTree )
        {
            wxFrame::SetMenuBar( detail::wxMenuBarFrom( commandTree ).release() );
        }

        void close()
        {
            wxFrame::Close( true );
        }
    };

    FrameWindow::FrameWindow( wchar_t const title[], VisibilityEnum visibility )
        : myImpl(
            boost::bind( &FrameWindow::onImplPtrVoided, this ),
            this,
            title,
            visibility
            )
    {}

    FrameWindow::~FrameWindow()
    {}

    void FrameWindow::onImplPtrVoided()
    {
        delete this;
        // TODO: replace with something like below.  Problem: access rights.
        //alfs::destroyPointee( this );       // Hara kiri.
    }

    void FrameWindow::onCommand( UserCommand::Id id )
    {
#       ifdef  _MSC_VER
            std::wostringstream s;

            ++nCommandEvents;
            s
                << L"Command event #" << nCommandEvents
                << L", id=" << long(id)
                << L"\r\n";
            ::OutputDebugString( s.str().c_str() );
#       endif

        myMainMenuActions.actionFor( id )();
    }

    void* FrameWindow::implementationObject() const
    {
        return static_cast<wxFrame*>( myImpl.validRawPointerOrZero() );
    }

    void FrameWindow::show()
    {
        myImpl->Show( true );
    }

    void FrameWindow::setMainMenu( InvocableUserCommandTree::Ptr commandTree )
    {
        ActionMap   oldMenuActions;

        oldMenuActions.swap( myMainMenuActions );

        try
        {
            ActionMap( commandTree ).swap( myMainMenuActions );
            myImpl->setMainMenu( commandTree );
        }
        catch( ... )
        {
            myMainMenuActions.swap( oldMenuActions );
            throw;
        }
    }

    void FrameWindow::close()
    {
        myImpl->close();
    }
}  // alfs
